package fr.bludwarf.skywarrior.global;

import java.text.SimpleDateFormat;
import java.util.Calendar;

import fr.bludwarf.skywarrior.evenements.Cycle;
import fr.bludwarf.skywarrior.evenements.Evenement;
import fr.bludwarf.skywarrior.exceptions.CoordonneesInconnues;
import fr.bludwarf.skywarrior.exceptions.EvenementInconnuOublie;
import fr.bludwarf.skywarrior.ile.Ile;
import fr.bludwarf.skywarrior.joueur.TypeVaisseau;
import fr.bludwarf.skywarrior.utils.TimeUtils;

// FIXME : créer un objet pour éviter tous les "_tempsRestants *= timeFactor" du code !!!
public class Temps {
	
	/** Format d'affichage des dates/heures réelles */
	public static final SimpleDateFormat SDF = new SimpleDateFormat("dd MMM HH:mm:ss");
	
	/** Durée d'un cycle en secondes */
	public static final int DUREE_CYCLE = 7 * 60 + 30;
	
	/** Temps infini pour un évènement */
	public static final double INFINI = -1;
	
	/** Temps écoulée depuis le début de la partie en secondes */
	double _tempsEcoule = 0;
	
	/** Date et heure réelle du début du temps */
	final Calendar _debut;
	
	public Temps(final Calendar debut) {
		_debut = debut;
	}
	
	public Temps(final Calendar debut, double tempsEcoule) {
		_debut = debut;
		_tempsEcoule = tempsEcoule;
	}
	
	public Temps(Temps tempsInitial, double temps) {
		_debut = tempsInitial._debut;
		_tempsEcoule = tempsInitial._tempsEcoule + temps;
	}

	@Override
	public String toString() {
		return toString(_tempsEcoule);
	}

	/**
	 * @return date réelle à cet instant à partir du début réel de la partie et du temps écoulé depuis
	 */
	public Calendar getDateReelle() {
		final Calendar date = (Calendar) _debut.clone();
		date.add(Calendar.SECOND, (int) Math.round(_tempsEcoule));
		return date;
	}
	
	public String formatDateReelle()
	{
		return SDF.format(getDateReelle().getTime());
	}
	
	@Override
	protected Object clone() {
		final Temps temps = new Temps(this._debut);
		temps._tempsEcoule = this._tempsEcoule;
		return temps;
	}

	public void avancer(final double temps) {
		_tempsEcoule += temps;
	}
	
	/**
	 * @return période restante du prochain cycle
	 */
	public Evenement prochainCycle(final EtatMonde etatMonde)
	{
		return new Cycle(tempsAvantProchainCycle());
	}
	
	/**
	 * @return Temps restant avant le prochain cycle
	 * @since 17 nov. 2011
	 */
	public double tempsAvantProchainCycle()
	{
		return DUREE_CYCLE - _tempsEcoule % DUREE_CYCLE;
	}
	
	/**
	 * @param ileDepart ile de départ
	 * @param ileArrivee ile d'arrivée
	 * @param vitesse vitesse du parcours dans l'unité de Skywar par seconde
	 * @return le temps pour parcourir une certaine distance
	 * @throws CoordonneesInconnues si les coordonnées d'une des deux iles sont inconnues
	 * @since 25 nov. 2011
	 */
	public static double tempsPourParcourir(final Ile ileDepart, final Ile ileArrivee, final int vitesse)
	throws CoordonneesInconnues
	{
		return ileDepart.distance(ileArrivee) * Coords.UNITE / vitesse;
	}
	
	/**
	 * Permet d'afficher un temps sous forme lisible
	 * @param temps temps en secondes
	 */
	public static String toString(final double temps)
	{
		return TimeUtils.timeToString(temps);
	}

	public double tempsEcouleDepuis(Temps temps) {
		return this._tempsEcoule - temps._tempsEcoule;
	}

	public static double tempsEcoule(final Calendar debut, final Calendar fin) {
		return (double) (fin.getTimeInMillis() - debut.getTimeInMillis()) / 1000;
	}

	public static double tempsEcoule(Temps t1, Temps t2) {
		return t2._tempsEcoule - t1._tempsEcoule;
	}
	
	/**
	 * Calcul le facteur d'accélération ou de ralentissement des temps de construction
	 * @param oldTime temps de construction d'un élément (ou même {@link #getInitialNextPop()}
	 * @return facteur d'accélération ou de ralentissement des temps de construction. Valeurs possibles :
	 * <ul>
	 * 	<li>&lt; 1 => accélération
	 * 	<li>= 1 => aucun changement
	 * 	<li>&gt; 1 => ralentissement
	 * </ul>
	 * @since 9 nov. 2011
	 */
	public static double getTimeFactor(final double oldTime, final double newTime)
	{
		return newTime / oldTime;
	}
	
	/**
	 * Calcul le facteur d'accélération ou de ralentissement des temps de construction
	 * @param oldPop ancienne population
	 * @return newPop nouvelle population
	 * <ul>
	 * 	<li>&lt; 1 => accélération
	 * 	<li>= 1 => aucun changement
	 * 	<li>&gt; 1 => ralentissement
	 * </ul>
	 * @since 24 nov. 2011
	 */
	public static double getTimeFactor(final int oldPop, final double newPop)
	{
		return Math.sqrt(oldPop) / Math.sqrt(newPop);
	}

	/**
	 * Calcul le facteur d'accélération ou de ralentissement des temps de construction
	 * @param oldNourriture ancienne nourriture
	 * @return newNourriture ancienne nourriture
	 * @return pop population actuelle
	 * <ul>
	 * 	<li>&lt; 1 => accélération
	 * 	<li>= 1 => aucun changement
	 * 	<li>&gt; 1 => ralentissement
	 * </ul>
	 * @since 24 nov. 2011
	 */
	public static double getTimeFactor(int oldNourriture, int newNourriture, int pop) {
		return (double) (oldNourriture - pop) / (newNourriture - pop);
	}

	/**
	 * @param pop
	 * @return TimeFactor pour un certain type
	 * @throws TimeFactorInconnu si le TimeFactor est inconnu pour le type demandé
	 * @since 10 nov. 2011
	 */
	public static double getTimeFactor(Class<?> clazz, int pop, final boolean architecte) throws TimeFactorInconnu {
		if (clazz == TypeVaisseau.class)
		{
			// FIXME : Time Factor Vaisseau : envoit d'exception si timeFactor inconnu
			double tf = Math.sqrt((double) 8 / pop);
			if (architecte)
			{
				tf *= 0.8;
			}
			return tf;
		}
		else
		{
			double tf = Math.sqrt((double) 8 / pop);
			if (architecte)
			{
				tf *= 0.7;
			}
			return tf;
		}
	}

	public static double applyTimeFactor(double factor, double tempsRestant) {
		if (tempsRestant == INFINI)
		{
			return INFINI;
		}
		else
		{
			return factor * tempsRestant;
		}
	}

	public static double decrementer(double tempsRestant, double temps) throws EvenementInconnuOublie {
		if (tempsRestant == INFINI)
		{
			return INFINI;
		}
		else if (tempsRestant >= temps)
		{
			return Math.max(0, tempsRestant - temps);
		}
		else
		{
			throw new EvenementInconnuOublie("Un évènement inconnu a été oublié");
		}
	}

	/**
	 * @return date/heure + temps écoulé
	 * @since 10 nov. 2011
	 */
	public String toCompleteString() {
		return formatDateReelle() + " (t = " + toString(_tempsEcoule) + ")";
	}

	public double tempsPourCollecter(int cout, int prod, int stocks) {
		// si stocks suffisants
		if (stocks >= cout)
		{
			return 0;
		}
		
		// si production insuffisante
		else if (cout != 0 && prod == 0)
		{
			return Temps.INFINI;
		}
		else
		{
			// Temps écoulé depuis le dernier cycle
			final double offset = DUREE_CYCLE - tempsAvantProchainCycle();
			final int nbCycles = (int) Math.ceil((double) (cout - stocks) / prod);
			return nbCycles * DUREE_CYCLE
				- offset; // arrondi au cycle précédent (pas besoin d'attendre plus)
		}
	}

	public static double applyTimeFactor(double ancienTempsTotal, double nouveauTempsTotal, double tempsRestant) {
		
		// FIXME : peut-être un problème de calcul, car tests KO
		return Temps.applyTimeFactor(getTimeFactor(ancienTempsTotal, nouveauTempsTotal), tempsRestant);
		
		// FIXME : cette fois le nextPop est un peu plus rapide que sur Skywar
		// On décrémente au nouveau nextPop le temps déjà passé sur le nextPop
//		return Math.max(nouveauTempsTotal - (ancienTempsTotal - tempsRestant), 0);
		
//		return nouveauTempsTotal;
	}

	/**
	 * @param t1 temps
	 * @param t2 temps
	 * @return somme des deux temps avec pris en compte du temps infini
	 * @since 28 nov. 2011
	 */
	public static double add(double t1, double t2) {
		if (t1 == INFINI || t2 == INFINI)
		{
			return INFINI;
		}
		else
		{
			return t1 + t2;
		}
	}
}
